package org.meanzoft.minecraft_clone;

import org.meanzoft.m3d.scene.camera.FreeCamera;
import org.meanzoft.minecraft_clone.util.Screen;
import org.meanzoft.minecraft_clone.world.BlockCompiler;
import org.meanzoft.minecraft_clone.block2.BlockGrass;
import org.meanzoft.minecraft_clone.world.Chunk;
import java.util.LinkedList;
import org.lwjgl.util.vector.Vector4f;
import org.meanzoft.minecraft_clone.map.Map;
import org.lwjgl.Sys;
import java.awt.Font;
import org.meanzoft.m3d.font.GLFont;
import org.meanzoft.minecraft_clone.effect.SkyBox;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.lwjgl.opengl.GL11;
import org.meanzoft.minecraft_clone.world.WorldManager;
import org.meanzoft.minecraft_clone.block2.Block;
import org.meanzoft.m3d.resource.ResourceManager;
import org.meanzoft.m3d.scene.Renderable;
import org.meanzoft.m3d.scene.SceneNode;
import org.meanzoft.m3d.InputManager;
import org.meanzoft.m3d.Viewport;
import org.lwjgl.util.glu.GLU;
import static org.lwjgl.opengl.GL11.*;

/**
 *
 * @author Meanz
 */
public class Game {

    public static Game getGame() {
        return game;
    }
    public static boolean gameRunning;
    private Viewport gameScreen;
    private InputManager gameInputManager;
    public FreeCamera gameCamera;
    private SceneNode gameRootNode;
    private SceneNode worldNode;
    private ResourceManager resourceManager;
    private WorldManager worldManager;
    private SkyBox skyBox;
    private static Game game;
    private GLFont fontLarge;
    private GLFont fontMedium;
    private GLFont fontSmall;
    private Map map;
    /**
     * Other vars
     */
    private boolean cullBack = true;
    private Block selectedBlock = null;
    private int fps = 0;
    private int dFPS = 0;
    private long lastFrame = 0;
    private long lastFPS = 0;
    private boolean selectEnable = false;
    private Vector4f lightPosition = new Vector4f(5f, -5f, 5f, 0f);
    private boolean isTyping = false;
    private String typingString = "";
    private LinkedList<Block> transpBlocks = new LinkedList<Block>();
    private int selectionRadius = 6;
    private int selectionAmount = selectionRadius * selectionRadius * selectionRadius;
    //private SelectionBlock[] selectionBlocks = new SelectionBlock[selectionAmount];
    //private LinkedList<SelectionBlock> blocks = new LinkedList<SelectionBlock>();

    public Game() {

        /**
         * Initial Components
         */
        gameRunning = true;
        game = this;
        gameScreen = new Viewport();
        gameScreen.setDisplayMode(800, 640);
        gameScreen.create();
        gameInputManager = new InputManager();
        gameInputManager.create();
        gameCamera = new FreeCamera();
        gameCamera.position.x = -5f;
        gameCamera.position.y = -2f;
        gameCamera.position.z = -5f;
        gameRootNode = new SceneNode();
        worldNode = new SceneNode();
        worldManager = new WorldManager();
        gameRootNode.attach(worldNode);
        new BlockCompiler();

        /**
         * Resources
         */
        resourceManager = new ResourceManager();
        resourceManager.loadTexture("blocks", "./textures/blocks.png");
        resourceManager.loadTexture("sky_0", "D:/Programming/DragonWays/textures/sky_0.png");
        resourceManager.loadTexture("sky_1", "D:/Programming/DragonWays/textures/sky_1.png");
        resourceManager.loadTexture("sky_2", "D:/Programming/DragonWays/textures/sky_2.png");
        resourceManager.loadTexture("sky_3", "D:/Programming/DragonWays/textures/sky_3.png");
        resourceManager.loadTexture("sky_4", "D:/Programming/DragonWays/textures/sky_4.png");
        resourceManager.loadTexture("sky_5", "D:/Programming/DragonWays/textures/sky_5.png");

        fontLarge = new GLFont(new Font("Arial", Font.BOLD, 18));
        fontMedium = new GLFont(new Font("Arial", Font.BOLD, 14));
        fontSmall = new GLFont(new Font("Arial", Font.BOLD, 10));

        for (int i = 0; i < selectionAmount; i++) {
            //selectionBlocks[i] = new SelectionBlock();
        }
        for (int x = 0; x < 512; x++) {
            for (int y = 0; y < 1; y++) {
                for (int z = 0; z < 512; z++) {
                    WorldManager.getWorld().addBlock(x, y, z, new BlockGrass());
                }
            }
        }
        map = new Map();
        /**
         * Effects
         */
        skyBox = new SkyBox();
        //light.setParams(GL_AMBIENT, new Vector4f(0.5f, 0.5f, 0.5f, 1f));
        /**
         * Variable init
         */
        lastFPS = getTime(); // call before loop to initialise fps timer
        getDelta(); // call once before loop to initialise lastFrame

        /**
         * OpenGL Init
         */
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glClearDepth(1.0f);
        glMatrixMode(GL_MODELVIEW);
        glShadeModel(GL_SMOOTH);
        glDepthFunc(GL_LEQUAL);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_ALPHA_TEST);
        glEnable(GL_LIGHTING);
        glEnable(GL_CULL_FACE);
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
        glEnable(GL_COLOR_MATERIAL);
        glAlphaFunc(GL_GREATER, 0.9f);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_BLEND);
        gameLoop();
    }

    public void gameLoop() {
        gameRunning = true;
        int TICKS_PER_SECOND = 60;
        int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
        int MAX_FRAMESKIP = 5;
        long next_game_tick = getTickCount();
        while (gameRunning) {
            try {
                int loops = 0;

                while (getTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
                    gameInput();
                    gameUpdate();

                    next_game_tick += SKIP_TICKS;
                    loops++;
                }

                gameRender();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        gameScreen.destroy();
        gameInputManager.destroy();
    }

    public long getTickCount() {
        return getTime();
    }
    int selBlockX = 0;
    int selBlockY = 0;
    int selBlockZ = 0;

    public void gameUpdate() {
        long t = getMS();
        updateFPS();
        updateNode(gameRootNode);

        /**
         * Selection
         */
        //blocks.clear();
        int xOff = (int) -gameCamera.position.x;
        int yOff = (int) -gameCamera.position.y;
        int zOff = (int) -gameCamera.position.z;

        int blockNo = 0;
        for (int x = xOff - selectionRadius; x < xOff + selectionRadius; x++) {
            for (int y = yOff - selectionRadius; y < yOff + selectionRadius; y++) {
                for (int z = zOff - selectionRadius; z < zOff + selectionRadius; z++) {
                    if (WorldManager.getWorld().getBlockAt(x, y, z) != null) {
                       // blocks.add(new SelectionBlock());
                       // blocks.getLast().setPosition(x, y, z + 1);
                       // blockNo++;
                    }
                }
            }
        }

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_FOG);
        glDisable(GL_LIGHTING);
        glLoadIdentity();
        glEnable3D();
        gameCamera.updateRender();
        //SelectionBlock[] block_array = new SelectionBlock[blocks.size()];
        //blocks.toArray(block_array);
        //for (SelectionBlock sb : block_array) {
        //    glPushMatrix();
        //    {
        //        sb.draw(false, false);
        //    }
        //    glPopMatrix();
        //}
        ByteBuffer pixel = Screen.get2dPixel(gameScreen.getWidth() / 2, gameScreen.getHeight() / 2);
        int selX = pixel.get(0);
        int selY = pixel.get(1);
        int selZ = pixel.get(2) - 1;
        gameScreen.setTitle("r: " + selX + " g: " + selY + " b: " + selZ);

        if (WorldManager.getWorld().getBlockAt(selX, selY, selZ) != null) {
            selBlockX = selX;
            selBlockY = selY;
            selBlockZ = selZ;
        }
        glEnable(GL_LIGHTING);
    }

    public void gameRender() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glLoadIdentity();

        glEnable3D();

        glDisable(GL_CULL_FACE);
        skyBox.render();
        if (cullBack && !glIsEnabled(GL_CULL_FACE)) {
            glEnable(GL_CULL_FACE);
        }
        gameCamera.updateRender();

        /**
         * Apply ambient lightning
         */
        ByteBuffer bb = ByteBuffer.allocateDirect(16);
        bb.order(ByteOrder.nativeOrder());
        bb.putFloat(0.5f).putFloat(0.5f).putFloat(0.5f).putFloat(1f).flip();
        glLightModel(GL_LIGHT_MODEL_AMBIENT, bb.asFloatBuffer());
        /**
         * Apply World Objects
         */
        /**
         * Apply Blocks
         */
        glPushMatrix();
        {
            glEnable(GL_TEXTURE_2D);
            renderBlocks();
        }
        glPopMatrix();

        /*SelectionBlock[] block_array = new SelectionBlock[blocks.size()];
        blocks.toArray(block_array);
        for (SelectionBlock sb : block_array) {
            glPushMatrix();
            {
                sb.draw(false, true);
            }
            glPopMatrix();
        }
         */
        renderNode(gameRootNode);
        /**
         * Apply Moving Objects
         */
        /**
         * Apply Player
         */
        /**
         * GUI
         */
        glEnable2D();
        glColor3f(1f, 0f, 0f);
        glBegin(GL_LINES);
        {
            glVertex2f(gameScreen.getWidth() / 2, gameScreen.getHeight() / 2 - 10);
            glVertex2f(gameScreen.getWidth() / 2, gameScreen.getHeight() / 2 + 10);

            glVertex2f(gameScreen.getWidth() / 2 - 10, gameScreen.getHeight() / 2);
            glVertex2f(gameScreen.getWidth() / 2 + 10, gameScreen.getHeight() / 2);
        }
        glEnd();
        glDisable2D();
        glColor3f(1.0f, 1.0f, 1.0f);
        fontLarge.print(10, 620, "MeanBlocks v 0.3");
        fontMedium.print(10, 600, "FPS: " + dFPS);
        fontMedium.print(10, 580, "X: " + -gameCamera.position.x);
        fontMedium.print(10, 560, "Y: " + -gameCamera.position.y);
        fontMedium.print(10, 540, "Z: " + -gameCamera.position.z);
        fontMedium.print(10, 520, "ChunkX: " + WorldManager.getWorld().getChunk((int)-gameCamera.position.x, (int)-gameCamera.position.y, (int)-gameCamera.position.z).getChunkX());
        fontMedium.print(10, 500, "ChunkY: " + WorldManager.getWorld().getChunk((int)-gameCamera.position.x, (int)-gameCamera.position.y, (int)-gameCamera.position.z).getChunkY());
        fontMedium.print(10, 480, "ChunkZ: " + WorldManager.getWorld().getChunk((int)-gameCamera.position.x, (int)-gameCamera.position.y, (int)-gameCamera.position.z).getChunkZ());
        fontMedium.print(10, 460, "NumBlocks in chunk: " + WorldManager.getWorld().getChunk((int)-gameCamera.position.x, (int)-gameCamera.position.y, (int)-gameCamera.position.z).getNumBlocks());
        fontMedium.print(10, 440, "Num of chunks in queue: " + BlockCompiler.getBlockCompiler().getWorkSize());

        if (isTyping) {
            fontMedium.print(10, 10, "Console: " + typingString + " |");
        } else {
            fontMedium.print(10, 10, "Console: ");
        }
        /**
         * Send to screen
         */
        gameScreen.refresh();
    }
    private float moveSpeed = 0.1f;

    public void gameInput() {
        gameInputManager.update();
        if (gameInputManager.isKeyDown(InputManager.KEY_ESCAPE)) {
            gameRunning = false;
            return;
        }
        /**
         * Camera Movement
         */
        gameCamera.pitch(gameInputManager.getDY() * 0.2f);
        gameCamera.yaw(gameInputManager.getDX() * 0.2f);

        if (gameInputManager.isMousePressed(InputManager.MOUSE_LMB)) {
            if (selBlockX != 0 || selBlockY != 0 || selBlockZ != 0) {
                try {
                    WorldManager.getWorld().removeBlock(selBlockX, selBlockY, selBlockZ);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
        if (gameInputManager.isKeyPressed(InputManager.KEY_END)) {
            isTyping = !isTyping;
        }
        if (!isTyping) {
            if (gameInputManager.isKeyPressed(InputManager.KEY_J)) {
                if (glIsEnabled(GL_NORMALIZE)) {
                    glDisable(GL_NORMALIZE);
                } else {
                    glEnable(GL_NORMALIZE);
                }
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_W)) {
                gameCamera.moveForward(moveSpeed);
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_S)) {
                gameCamera.moveBackwards(moveSpeed);
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_A)) {
                gameCamera.strafeLeft(moveSpeed);
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_D)) {
                gameCamera.strafeRight(moveSpeed);
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_Q)) {
                gameCamera.position.y += moveSpeed;
            }
            if (gameInputManager.isKeyDown(InputManager.KEY_E)) {
                gameCamera.position.y -= moveSpeed;
            }
            if (gameInputManager.isKeyPressed(InputManager.KEY_T)) {
                selectEnable = !selectEnable;
            }
            if (gameInputManager.isKeyPressed(InputManager.KEY_C)) {
                cullBack = !cullBack;
                if (glIsEnabled(GL_CULL_FACE)) {
                    glDisable(GL_CULL_FACE);
                } else {
                    glEnable(GL_CULL_FACE);
                }
            }
        } else {
            if (gameInputManager.getPressedKeys().size() > 0) {
                if (gameInputManager.isKeyPressed(InputManager.KEY_BACK)) {
                    if (typingString.length() > 0) {
                        byte[] b = typingString.getBytes();
                        b[b.length - 1] = 0;
                        byte[] c = new byte[b.length - 1];
                        for (int i = 0; i < b.length - 1; i++) {
                            c[i] = b[i];
                        }
                        typingString = new String(c);
                    }
                }
                typingString += gameInputManager.getPressedText();
            }
        }
    }

    public void renderBlocks() {
        ResourceManager.getManager().getTexture("blocks").bind();
        for (Chunk c : WorldManager.getWorld().getChunks((int) -gameCamera.position.x, (int) -gameCamera.position.y, (int) -gameCamera.position.z, 12)) {
            if (c != null) {
                glPushMatrix();
                {
                    glTranslatef(c.getChunkX() * 16, c.getChunkY() * 16, c.getChunkZ() * 16);
                    if (c.isRequestingRebuild()) {
                        c.build();
                    } else {
                        c.draw();
                    }
                }
                glPopMatrix();
            }
        }
    }

    public void updateNode(SceneNode node) {
        //if (node instanceof SceneObject) {
            //((SceneObject) node).update();
        //}
        for (SceneNode children : node.getChildren()) {
            updateNode(children);
        }
    }

    public void renderNode(SceneNode node) {
        if (node instanceof Renderable) {
            glPushMatrix();
            {
                glMatrixMode(GL_MODELVIEW);
                glScalef(node.localScale.x, node.localScale.y, node.localScale.z);
                glRotatef(node.getPitch(), 1f, 0f, 0f);
                glRotatef(node.getYaw(), 0f, 1f, 0f);
                glTranslatef(node.position.x, node.position.y, node.position.z);
                ((Renderable) node).render();
            }
            glPopMatrix();
        } else {
            for (SceneNode children : node.getChildren()) {
                renderNode(children);
            }
        }
    }

    /**
     * Viewport functions
     */
    public void glEnable3D() {
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_ALPHA_TEST);
        glMatrixMode(GL_PROJECTION);
        {
            glLoadIdentity();
            GLU.gluPerspective(40f, 1f, 0.01f, 256f);
        }
        glMatrixMode(GL_MODELVIEW);
    }

    public void glEnable2D() {
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_ALPHA_TEST);
        glMatrixMode(GL_PROJECTION);
        {
            GL11.glPushMatrix();                   // preserve perspective view
            glLoadIdentity();
            glOrtho(0, gameScreen.getWidth(), gameScreen.getHeight(), 0, -500, 500);
        }
        glMatrixMode(GL_MODELVIEW);
        GL11.glPushMatrix();				   // Preserve the Modelview Matrix
        GL11.glLoadIdentity();				   // clear the Modelview Matrix
    }

    public void glDisable2D() {
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glPopMatrix();
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glPopMatrix();
        // turn Depth Testing back on
        GL11.glEnable(GL11.GL_DEPTH_TEST);
    }

    /**
     * Time functions
     */
    public int getDelta() {
        long time = getTime();
        int delta = (int) (time - lastFrame);
        lastFrame = time;
        return delta;
    }

    public long getMS() {
        return System.currentTimeMillis();
    }

    public long getTime() {
        return (Sys.getTime() * 1000) / Sys.getTimerResolution();
    }

    public void updateFPS() {
        if (getTime() - lastFPS > 1000) {
            dFPS = fps;
            fps = 0;
            lastFPS += 1000;
        }
        fps++;
    }

    /**
     * TODO CLEANUP
     */
    public void updateSelect() {
        if (selectEnable) {
            /*glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glDisable(GL_TEXTURE_2D);
            glDisable(GL_FOG);
            glDisable(GL_LIGHTING);
            glLoadIdentity();
            glMatrixMode(GL_PROJECTION);
            {
            glLoadIdentity();
            GLU.gluPerspective(40f, 1f, 0.01f, 100f);
            }
            glMatrixMode(GL_MODELVIEW);
            gameCamera.update3DRender();
            for (Block b : WorldManager.getWorld().
            getChunk(
            (int) gameCamera.position.x,
            (int) gameCamera.position.y,
            (int) gameCamera.position.z).getBlocks()) {
            glPushMatrix();
            {
            glMatrixMode(GL_MODELVIEW);
            glScalef(0.5f, 0.5f, 0.5f);
            glTranslatef(b.position.x, b.position.y, b.position.z);
            glColor3f(b.getPosition().x / 255, b.getPosition().y / 255, (b.getPosition().z + 1) / 255);
            b.draw();
            }
            glPopMatrix();
            }
            ByteBuffer pixel = Screen.get2dPixel(gameScreen.getWidth() / 2, gameScreen.getHeight() / 2);
            gameScreen.setTitle("r: " + pixel.get(0) + " g: " + pixel.get(1) + " b: " + (pixel.get(2) - 1));
            
            if (WorldManager.getWorld().getBlockAt(pixel.get(0), pixel.get(1), pixel.get(2) - 1) != null) {
            selectedBlock = WorldManager.getWorld().getBlockAt(pixel.get(0), pixel.get(1), pixel.get(2) - 1);
            selectedBlock.select();
            }
            glEnable(GL_LIGHTING);*/
        }
    }
}
